package main;

import java.awt.Desktop;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;






import search.SearchTuple;
import search.TagedFiles;
import tagSystem.TagAndSearchManager;
import tray.TrayIcon;
import message.FileWasChanged;
import message.Info;
import message.Message;
import message.MessageType;
import message.NewBox;
import message.SendFile;
import message.ShareBox;
import message.StartSync;
import message.StopComunication;
import message.StopSync;
import RMI.RemoteImp;
import Watcher.ChangeType;
import Watcher.Watcher;
import Watcher.WatcherObserver;
import configuration.Config;
import configuration.ConfigOperator;
import configuration.CreatXMLFile;
import connections.ContactToServer;
import connections.RegisterToServer;


public class Client implements WatcherObserver{
	private static Client clientInstance= new Client();
	private Config configFile;
	private TrayIcon trayIcon;
	private Watcher watcher;
	private PriorityQueue<Message> queue;
	private HashMap<String, Message> changedFiles;
	private ContactToServer contactToServer;
	private UserInfo userInfo;
	private boolean isListening=false;
	private TagAndSearchManager tagAndSearchManager;
	private PrintStream err;
	private PrintStream out;
	public static Client getClientInstance() {
		return clientInstance;
	}	

	private Client() {}

	/**
	 * Initialize all Client service component and start the service 
	 * @param args
	 * @throws IOException 
	 */
	public void start() throws IOException {
		configFile=ConfigOperator.read("C:\\Program Files\\project\\ClientService\\Config.xml");

		//redirection of error and output to a log
		/*err = new PrintStream(new BufferedOutputStream(new FileOutputStream("errLog.txt")));
		out = new PrintStream(new BufferedOutputStream(new FileOutputStream("outLog.txt")));
		System.setOut(out);
		System.setErr(err);*/

		queue = new PriorityQueue<Message>();
		changedFiles = new HashMap<String, Message>();
		contactToServer = new ContactToServer();
		userInfo=UserInfo.getInstance();
		userInfo.config(configFile);
		RemoteImp remote = new RemoteImp();
		Thread remoteThread = new Thread(remote);
		remoteThread.start();
		System.out.println("wwwwwwwwwwwwww");
		/*trayIcon = new TrayIcon();
		Thread icon = new Thread(trayIcon);
		icon.start();*/
		isListening=true;
		addMessage(new Info(null, null, null, null));
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		while(!userInfo.isUpdate());
		System.out.println("OUT OF WHILE");
		createFolders(userInfo.getBoxesList());
		startWatcher(userInfo.getBoxesList());
		System.out.println("barak kind");

	}
	/*
	 * create new folders in client computer
	 * @param folders list of folders to create
	 */
	private void createFolders(Set<String> folders){
		for(String dir : folders){
			File f1 = new File(userInfo.getPath()+"\\"+dir);
			if(f1.mkdir())
				System.out.println("Directory Created");
			else
				System.out.println("Directory is not created");
			//remoteInt.startWatcher(path+"\\CleverDropBox\\"+dir);


		}
	}
	/**
	 * stop the service
	 */
	public void stop() {
		Config config = userInfo.getConfig();
		ConfigOperator.write(config, "Config.xml");
		/*err.close();
		out.close();*/
	}
	/*
	 * start to watch changes in directory
	 * @param paths path of folder to watch
	 */
	private void startWatcher(Set<String> paths){
		try {
			watcher = new Watcher();
			//sign this class as observer to watcher
			watcher.registerObserver(this);
			Thread watcherThread = new Thread(watcher);
			watcherThread.start();
			//define watcher on the paths
			for(String path : paths){
				System.out.println(path);
				Path _directotyToWatch = Paths.get(userInfo.getPath()+"\\"+path);
				watcher.addDirectory(_directotyToWatch);
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/*
	 * generate checksum of file
	 * @param fileName 
	 * @return the generated checksum
	 */
	private long getChecksum(String fileName){
		try {

			CheckedInputStream cis = null;
			// Computer CRC32 checksum
			FileInputStream fileInputStream = new FileInputStream(fileName);
			cis = new CheckedInputStream(fileInputStream, new CRC32());            
			byte[] buf = new byte[128];
			while(cis.read(buf) >= 0) {}
			fileInputStream.close();
			return cis.getChecksum().getValue();            

		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
		return -1;
	}
	/*
	 * return file size 
	 */
	private long getFileSize(String fileName){
		File file = new File(fileName);
		return file.length();

	}
	/*
	 * return file last modify date
	 */
	private Date getFileModDate(String fileName){
		File file = new File(fileName);
		return new Date(file.lastModified());

	}
	/**
	 * Something accord in the folder that we listen to 
	 */
	public void somethingChanged(Path path, Path fileName,Path lastFileName,ChangeType type){
		try {
			Thread.sleep(250);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("path: " + path + "\nfile name: " + fileName + "\ntype: "+type);

		Message message = null;
		String boxName = path.toString();
		int lastIndex = boxName.lastIndexOf("\\");
		boxName = boxName.substring(lastIndex+1);
		System.out.println("box name : " + boxName);
		//temporal files
		if(fileName.startsWith("~")) return;

		tagAndSearchManager = TagAndSearchManager.getInst();
		//Correspondence the type of change to the right case 
		switch(type){
		case RENAME:

			tagAndSearchManager.removeTags(path+"\\"+lastFileName);			
		case CREATE:
		case MODIFY:
			File file = new File(path+"\\"+fileName);			
			if(!file.exists()) break;			
			System.out.println(path.toString());			
			//tag the file
			ArrayList<TagedFiles> allTages = tagAndSearchManager.tagFile(path+"\\"+fileName);
			//send message to server
			message = new FileWasChanged(fileName.toString(),getChecksum(path.toString()+"\\"+fileName.toString()),getFileSize(path.toString()+"\\"+fileName.toString()),boxName,getFileModDate(path.toString()+"\\"+fileName.toString()),allTages,type);
			break;
		case DELETE:
			tagAndSearchManager.removeTags(path+"\\"+fileName);
			//send message to server
			message = new FileWasChanged(fileName.toString(),-1,-1,boxName,null,null,type);
			break;
		}

		System.out.println("here");
		//add message to message queue
		if(message!=null)
			addMessage(message);
	}
	/**
	 * add message to server to a queue
	 * @param message the message to add
	 */
	public void addMessage(Message message){

		if(message.getType()==MessageType.FILE_WAS_CHANGED){
			FileWasChanged fileChanged = (FileWasChanged)message;
			changedFiles.put(fileChanged.getBoxName()+"/"+fileChanged.getFileName(), message);
		}
		else{
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			queue.add(message);
		}
		//can send new message to server
		if(!contactToServer.isConnected() && isListening){
			try {
				contactToServer.sendMassege(getNextMessage());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	/*
	 * return if queue is empty or not
	 */
	public boolean isMsgWait(){
		return (!queue.isEmpty() || !changedFiles.isEmpty());
	}
	/**
	 * return the next message in queue
	 */
	public Message getNextMessage(){
		if(!queue.isEmpty())
			return queue.poll();
		if(!changedFiles.isEmpty()){
			Set<String> set =  changedFiles.keySet();
			String first = set.iterator().next();
			return changedFiles.remove(first);
		}
		return null;
	}
	/**
	 * register to server
	 */
	public void registerToServer() {
		RegisterToServer registerToServer = new RegisterToServer(this);
		Thread t = new Thread(registerToServer);
		t.start();
		//registerToServer.register(userInfo.getUserName(), userInfo.getPass());
	}
	/**
	 * 
	 * @param fileName 
	 * @return the Bytes in file
	 */
	public byte[] getFileBytes(String fileName){
		byte [] byteArray=null;
		try {
			File file = new File (fileName);
			byteArray  = new byte [(int)file.length()];
			FileInputStream fis = new FileInputStream(file);
			BufferedInputStream bis = new BufferedInputStream(fis);
			bis.read(byteArray);
			bis.close();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return byteArray;
	}
	/**
	 * create new file and put it in location that specified
	 * @param boxName folder name
	 * @param fileName file name
	 * @param byteArray the data of file
	 */
	public void createFile(String boxName, String fileName, byte[] byteArray){
		FileOutputStream fos;
		try {
			fos = new FileOutputStream(userInfo.getPath()+"\\"+boxName+"\\"+fileName);
			BufferedOutputStream bos = new BufferedOutputStream(fos);
			bos.write(byteArray);
			bos.flush();
			bos.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		//trayIcon.writeMessage("File was changed", "file was changed in box "+boxName);
	}
	/*
	 * check if file exist in folder
	 */
	private File isFileExistInList(String boxName,final String fileName){
		File box = new File(userInfo.getPath()+"\\"+boxName);

		// This filter only returns directories
		FileFilter fileFilter = new FileFilter() {
			public boolean accept(File file) {
				return file.getName().equals(fileName);
			}
		};
		File[] files = box.listFiles(fileFilter);
		System.out.println("length: "+files.length);
		if(files.length!=0)
			return files[0];
		return null;
	}
	/**
	 * deal with fileWasChanged message from server
	 * @param fileWasChanged
	 * @return message to send to server
	 */
	public Message fileWasChanged(FileWasChanged fileWasChanged){
		//the same file exist in folder
		File file = isFileExistInList(fileWasChanged.getBoxName(), fileWasChanged.getFileName());
		//the file exist and the change type is not rename
		if(file!=null&&fileWasChanged.getChangeType()!=ChangeType.RENAME){
			System.out.println("checksum: "+ fileWasChanged.getCheckSum());
			Date modDate = new Date(file.lastModified());
			//the change type is not delete & the file in message is the updated file
			if(fileWasChanged.getCheckSum()!=-1&&fileWasChanged.getModDate().after(modDate)){
				return new SendFile(fileWasChanged.getBoxName(), fileWasChanged.getFileName(), fileWasChanged.getCheckSum(), fileWasChanged);
			}
			//need to delete file
			else{
				System.out.println("delete file!!!!!!!!1");
				file.delete();
				System.out.println("after delete file!!!!!!!!1");
			}	
		}
		else{
			//change type is rename
			if(fileWasChanged.getChangeType()==ChangeType.RENAME){
				File box = new File(userInfo.getPath()+"\\"+fileWasChanged.getBoxName());
				String[] list = box.list();
				for(String fileName : list){
					if(getChecksum(userInfo.getPath()+"\\"+fileWasChanged.getBoxName()+"\\"+fileName)==fileWasChanged.getCheckSum()){
						File renameFile = new File(userInfo.getPath()+"\\"+fileWasChanged.getBoxName()+"\\"+fileName);
						renameFile.renameTo(new File(userInfo.getPath()+"\\"+fileWasChanged.getBoxName()+"\\"+fileWasChanged.getFileName()));
					}
				}
			}
			//get the new file from server
			else if(fileWasChanged.getCheckSum()!=-1)
				return new SendFile(fileWasChanged.getBoxName(), fileWasChanged.getFileName(), fileWasChanged.getCheckSum(), fileWasChanged); 
		}
		return new StopComunication();
	}
	/**
	 * stop Listening to file system
	 */
	public void stopListening(){
		isListening=false;
		addMessage(new StopSync());

	}
	/**
	 * start Listening to file system
	 */
	public void startListening(){
		isListening=true;
		try {
			contactToServer.sendMassege(new StartSync());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	/**
	 * send message to server of creating new folder
	 * @param boxName
	 */
	public void createNewBox(String boxName) {
		if(createNewFolder(boxName))
			addMessage(new NewBox(boxName));

	}
	/**
	 * create new folder in file system
	 * @param boxName folder name
	 * @return success or failure
	 */
	public boolean createNewFolder(String boxName) {
		System.out.println(userInfo.getPath()+" , "+boxName);
		File f1 = new File(userInfo.getPath()+"\\"+boxName);
		try{
			if(f1.mkdir()){
				System.out.println("Directory Created");
				watcher.addDirectory(Paths.get(userInfo.getPath()+"\\"+boxName));
				this.userInfo.addBox(boxName);
				return true;
			}
			else
				System.out.println("Directory is not created");
		}catch (Exception e) {
			// TODO: handle exception
		}
		return false;

	}
	/**
	 * send message of new shared folder to server
	 * @param emails list of all users the client want to share the folder with
	 * @param boxName the name of the shared folder
	 */
	public void shareBox(ArrayList<String> emails, String boxName) {
		addMessage(new ShareBox(emails,boxName));
		userInfo.addBox(boxName);


	}
	/**
	 * add new plug in of tagging algorithm to client tag algorithm list
	 * @param path location of the class file with the algorithm
	 * @param className the name of the algorithm class
	 * @return the optional tags and the tag type of the algorithm
	 */
	public ArrayList<String> addPlugIn(String path, String className) {
		TagAndSearchManager t=TagAndSearchManager.getInst();
		//adding the plug to list
		ArrayList<String> tagList = t.addPlugToList(path, className);
		if(tagList!=null){
			//adding the plug to DB
			t.addPlugToDB(path, className,tagList.get(0));
			//tag all files in the system
			String mainFolderPath = userInfo.getPath();
			Set<String>boxes = userInfo.getBoxesList();
			for(String box : boxes){
				File file = new File(mainFolderPath+"\\"+box);
				String[] fileList = file.list();
				for(String fileName : fileList){
					System.out.println("folder : "+ fileName);
					t.tagFile(mainFolderPath+"\\"+box+"\\"+fileName);
				}
			}
		}
		return tagList;


	}
	/**
	 * search files with specific tag & tag type in system 
	 * @param tagTypeList
	 * @param tag
	 * @return search results
	 */
	public ArrayList<SearchTuple> search(ArrayList<String> tagTypeList, String tag) {
		TagAndSearchManager t=TagAndSearchManager.getInst();
		ArrayList<SearchTuple> ret=null;
		//search the files in database
		ret=t.searchInDataBase(tagTypeList,  tag);
		//empty result
		if(ret==null)
			return null;
		//compleate missing fields
		for(SearchTuple ft : ret){
			String curPath=ft.getFileName();
			File file = new File (curPath);
			ft.setLastModify(new Date(file.lastModified()));
			ft.setSize((int)file.length());
			int index=ft.getFileName().lastIndexOf("\\");
			System.out.println("index1:"+index);
			String fileName=curPath.substring(index+1);
			System.out.println("fileName1:"+fileName);
			curPath=curPath.substring(0,index);
			System.out.println("pathAfter1:"+curPath);
			String boxName=curPath.substring(curPath.lastIndexOf("\\")+1);
			System.out.println("BoxName1:"+boxName);
			ft.setFileName(fileName);
			ft.setBoxName(boxName);
		}
		return ret;
	}
	/**
	 * open file that in file system
	 * @param path the location of file
	 */
	public void openFile(String path) {
		try {
			Desktop.getDesktop().open(new File(userInfo.getPath()+"\\"+path));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	/**
	 * search file in system with specific tags
	 * @param tag the tags to search
	 * @return
	 */
	public ArrayList<SearchTuple> advencedSearch(ArrayList<String> tag){
		TagAndSearchManager t=TagAndSearchManager.getInst();
		ArrayList<SearchTuple> allTuples=t.advencedSearchInDataBase(tag);
		//empty result
		if(allTuples==null)
			return null;
		//compleate missing fields
		for(SearchTuple ft : allTuples){
			String curPath=ft.getFileName();
			File file = new File (curPath);
			ft.setLastModify(new Date(file.lastModified()));
			ft.setSize((int)file.length());
			int index=ft.getFileName().lastIndexOf("\\");
			System.out.println("index1:"+index);
			String fileName=curPath.substring(index+1);
			System.out.println("fileName1:"+fileName);
			curPath=curPath.substring(0,index);
			System.out.println("pathAfter1:"+curPath);
			String boxName=curPath.substring(curPath.lastIndexOf("\\")+1);
			System.out.println("BoxName1:"+boxName);
			ft.setFileName(fileName);
			ft.setBoxName(boxName);
		}
		return allTuples;
	}
	/**
	 * set the algorithm that will be use from now to tag new files
	 * @param tagTypes
	 */
	public void changeTagAlgoValid(ArrayList<String> tagTypes) {
		TagAndSearchManager t=TagAndSearchManager.getInst();
		t.changeTagAlgo(tagTypes);

	}
	/**
	 * get all tagging algorithm
	 * @return list of tag type and optional tags for every algorithm
	 */
	public ArrayList<ArrayList<String>> getPlugins() {
		TagAndSearchManager t = TagAndSearchManager.getInst();
		return t.getPlugins();
	}

}
